#!/usr/bin/python
#
# Classes to manage creation of logs

import datetime
import pygtk
pygtk.require('2.0')
import gobject
import gtk
import os
import re
import xml.dom.minidom

date_format = '%d %B %Y'
time_format = '%H:%M:%S'
datetime_format = '%s %s' % (date_format, time_format)
width = 500
height = 300

class Event(object):
    """Application events stored in the log."""
    __slots__ = ['type', 'text', 'data', 'time']

    @classmethod
    def from_xml(cls, node):
        """Return an event from an xml node."""
        #self.set_id(int(node.getAttribute('id')))
        data = None
        for child_node in node.childNodes:
            if child_node.tagName == 'time':
                time_text = child_node.firstChild.nodeValue
                time = datetime.datetime.strptime(time_text, datetime_format)
            elif child_node.tagName == 'type':
                type = child_node.firstChild.nodeValue
            elif child_node.tagName == 'text':
                text = child_node.firstChild.nodeValue
            elif child_node.tagName == 'data':
                data = child_node.firstChild.nodeValue
            elif child_node.tagName == 'data_type':
                data_type = child_node.firstChild.nodeValue

        if data and data_type:
            data = eval('%s(%s)' % (data_type, data))

        return cls(type, text, data)
        
    def __init__(self, event_type, text, data=None):
        """Set the event time to the current time."""
        self.time = datetime.datetime.now()
        self.type = event_type
        self.text = text
        self.data = data
    
    def type_markup(self):
        """Return a textual representation of the event type."""
        if self.type == 'Error':
            result = '<span foreground="red" weight="700"><b>%s</b></span>' % (self.type)
        elif self.type == 'Warning':
            result = '<span foreground="orange" weight="700">%s</span>' % (self.type)
        elif self.type == 'Information':
            result = '<span foreground="green">%s</span>' % (self.type)
        else:
            result = self.type
        return result

    def time_markup(self):
        """Return the time of this event as a string."""
        return self.time.strftime(datetime_format)

    def to_xml(self, document):
        """Return an xml node representing this event."""
        event_node = document.createElement('event')
        #event_node.setAttribute('id', str(self.get_id()))

        if self.time:
            time_node = document.createElement('time')
            time = self.time.strftime(datetime_format)
            time_node.appendChild(document.createTextNode(time))
            event_node.appendChild(time_node)

        if self.type:
            type_node = document.createElement('type')
            type_node.appendChild(document.createTextNode(str(self.type)))
            event_node.appendChild(type_node)

        if self.text:
            text_node = document.createElement('text')
            text_node.appendChild(document.createTextNode(str(self.text)))
            event_node.appendChild(text_node)

        if self.data:
            data_node = document.createElement('data')
            data_node.appendChild(document.createTextNode(str(self.data)))
            event_node.appendChild(data_node)

        return event_node

class Event_Log(object):
    """Log of events performed."""

    @classmethod
    def from_xml(cls, node):
        """Load the log from an XML node."""
        events = []
        title = None
        for child_node in node.childNodes:
            if child_node.tagName == 'title':
                title = child_node.firstChild.nodeValue
            elif child_node.tagName == 'event':
                events.append(Event.from_xml(child_node))

        new_log = cls(title)
        if events:
            new_log.content = events
        return new_log

    def __init__(self, title=None):
        """Initialise to empty list."""
        self.content = []
        self.title = title

    def __len__(self):
        """Return the numer of items."""
        return len(self.content)

    def append(self, event):
        """Add event to list."""
        self.content.append(event)
        Log_Dialog.popup(self, event)

    def type_filter(self, types):
        """Return a filtered version of the list based on the event type."""
        if types:
            return [item for item in self.content if item.type in types]
        else:
            return self.content
    
    def to_xml(self, document):
        """Converts the log to an XML node."""
        log_node = document.createElement('log')
        #event_node.setAttribute('id', str(self.get_id()))

        time = datetime.datetime.now().strftime(datetime_format)
        if self.title:
            title = "%s: %s" % (self.title, time)
        else:
            title = "Event Log: %s" % (time)
        title_node = document.createElement('title')
        title_node.appendChild(document.createTextNode(str(title)))
        log_node.appendChild(title_node)

        for event in self.content:
            log_node.appendChild(event.to_xml(document))

        return log_node

class Log_Dialog(gtk.Dialog):
    """Log display dialog showing events."""
    log_views = {}
    popup_patterns = ['Warning', 'Error']

    @classmethod
    def show(cls, log):
        """Display the window for the given log, creating it if necessary."""
        if log in cls.log_views:
            cls.log_views[log].update()
            cls.log_views[log].present()
        else:
            cls.log_views[log] = cls(log)

    @classmethod
    def popup(cls, log, event):
        """Display the window for the given log, if the even matches the popup test."""
        popup = False
        for pattern in cls.popup_patterns:
            if event.type.find(pattern) != -1:
                popup = True
                break

        if popup:
            cls.show(log)
        elif log in cls.log_views:
            cls.log_views[log].update()

    def __init__(self, log):
        """Initialisation of dialog."""
        if log and log.title:
            title = log.title
        else:
            title = "Event Log"
        gtk.Dialog.__init__(self, title, None, 0,
                            (gtk.STOCK_CLEAR, gtk.RESPONSE_ACCEPT,
                             gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
        self.set_default_size(width, height)
        self.connect('response', self.response_cb)
        self.log = log
        self.types = set([event.type for event in self.log.content])
        self.filter = None
        window = gtk.ScrolledWindow()
        self.content = gtk.ListStore(object, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
        self.view = gtk.TreeView(self.content)
        window.add(self.view)
        self.view.get_selection().set_mode(gtk.SELECTION_SINGLE)

        text_renderer = gtk.CellRendererText()
        tree_column = gtk.TreeViewColumn('Type', text_renderer, markup = 1)
        tree_column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        self.view.append_column(tree_column)

        text_renderer = gtk.CellRendererText()
        tree_column = gtk.TreeViewColumn('Description', text_renderer, text = 2)
        tree_column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        self.view.append_column(tree_column)

        text_renderer = gtk.CellRendererText()
        tree_column = gtk.TreeViewColumn('Time', text_renderer, text = 3)
        tree_column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        self.view.append_column(tree_column)
        self.connect('popup-menu', self.popup_cb)
        self.view.connect('button-press-event', self.button_cb)
        
        self.populate_list()
        self.vbox.pack_start(window)
        self.show_all()

    def populate_list(self):
        """Sets the list based on the content of the event log."""
        self.content.clear()
        for event in self.log.type_filter(self.filter):
            self.content.append((event, event.type_markup(), event.text, event.time_markup()))

    def popup_cb(self, widget):
        """Populates and displays the popup menu."""
        print 'pop-up'
        menu = gtk.Menu()
        item = gtk.MenuItem('Filter')
        menu.append(item)
        filter_menu = gtk.Menu()
        item.set_submenu(filter_menu)        
        for event_type in self.types:
            item = gtk.CheckMenuItem('Show %s events' % (event_type))
            filter_menu.append(item)
        if self.types:
            filter_menu.append(gtk.SeparatorMenuItem())
        item = gtk.MenuItem('Custom Filter')
        filter_menu.append(item)

        menu.append(gtk.SeparatorMenuItem())
        item = gtk.MenuItem('Load Log')
        item.connect('activate', self.load_cb)
        menu.append(item)
        item = gtk.MenuItem('Save Log')
        item.connect('activate', self.save_cb)
        menu.append(item)

        menu.show_all()
        menu.popup(None, None, None, 1, gtk.get_current_event_time())

    def button_cb(self, widget, event):
        """If right button display context menu."""
        if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
            self.popup_cb(widget)

    def update(self):
        """Update the contents of the view."""
        if len(self.log) > len(self.content):
            for index in range(len(self.content), len(self.log)):
                event = self.log.content[index]
                self.content.append((event,
                                     event.type_markup(),
                                     event.text,
                                     event.time_markup()))
                self.types.add(event.type)

    def response_cb(self, widget, response):
        """Close window and remove for log view list."""
        if response == gtk.RESPONSE_CLOSE:
            del self.log_views[self.log]
            self.destroy()
        elif response == gtk.RESPONSE_ACCEPT:
            self.log.content = []
            self.content.clear()
            self.update()

    def load_cb(self, menuitem):
        """Handle a 'load file' menu request"""
        self.load_log()

    def load_log(self):
        """Load a log file into a new dialog."""
        dialog = gtk.FileChooserDialog("Save Log", None, gtk.FILE_CHOOSER_ACTION_OPEN,
                                       (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                        gtk.STOCK_OPEN, gtk.RESPONSE_OK))
        filename = None
        response = dialog.run()
        if response == gtk.RESPONSE_OK:
            filename =  dialog.get_filename()
        dialog.destroy()
        if filename and os.path.exists(filename):
            pattern = re.compile("\s*(<|>)\s*")
            file = open(filename)
            xml_string = file.read()
            file.close()
            # Remove pretty printing
            xml_string = pattern.sub(r'\1', xml_string)
            document = xml.dom.minidom.parseString(xml_string)
            Log_Dialog.show(Event_Log.from_xml(document.firstChild))

    def save_cb(self, menuitem):
        """Handle a 'save file' menu request"""
        self.save_log()

    def save_log(self):
        """Save the contents of the log file."""
        dialog = gtk.FileChooserDialog("Save Log", None, gtk.FILE_CHOOSER_ACTION_SAVE,
                                       (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                        gtk.STOCK_SAVE, gtk.RESPONSE_OK))
        filename = None
        response = dialog.run()
        if response == gtk.RESPONSE_OK:
            filename =  dialog.get_filename()
        dialog.destroy()
        if filename:
            xml_document = xml.dom.minidom.getDOMImplementation().createDocument(None, None, None)
            xml_document.appendChild(self.log.to_xml(xml_document))

            file = open(filename, 'w')
            file.write(xml_document.toprettyxml())
            file.close()

log = Event_Log()
